// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: chunhui

#include <unordered_map>

#include <cxxopts.hpp>
#include <glog/logging.h>

#include "cc/appcoin2/support/feed/interval_feed_tool/interval_feed_consumer.h"
#include "cc/appcoin2/support/feed/interval_feed_tool/util.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/feed.pb.h"
#include "coin2/exchange/interval_feed_enum.pb.h"
#include "coin2/strategy/strategy.h"

namespace interval_feed {

using namespace coin2::app;
using namespace coin2::base::config;
using namespace coin2::exchange::interval_feed_enum;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::market::MarketExchangeApi;

class GeneratingIntervalFeedStrategy : public ::coin2::strategy::IStrategy {
 public:
  explicit GeneratingIntervalFeedStrategy(const IntervalFeedConfig& config) : config_(config) {
    int sec_in_duartion = config_.duration / 1'000'000'000LL;
    int n_intervals = sec_in_duartion / config.resolution;
    if (sec_in_duartion % config.resolution > 0) {
      n_intervals++;
    }
    for (const auto& [mea_str, outfile] : config.outfiles) {
      MarketExchangeApi mea = MarketExchangeApi::FromString(mea_str);
      outfiles_[mea] = outfile;
      consumers_[mea] =
          std::make_unique<IntervalFeedConsumer>(config.start_ts, config.resolution, n_intervals);
    }
  }

  void onBookFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::BOOK_FEED);
  }
  void onTradeFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::TRADE_FEED);
  }
  void onOpenInterestFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::OPEN_INTEREST_FEED);
  }
  void onLiquidationFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::LIQUIDATION_FEED);
  }
  void onNavFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::NAV_FEED);
  }
  void onMarkPriceFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::MARK_PRICE_FEED);
  }
  void onIndexFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::INDEX_FEED);
  }
  void onFundingRateFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::FUNDING_RATE_FEED);
  }

  void onMarketAnalysisFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::MARKET_ANALYSIS_FEED);
  }
  void onTopLongShortAccountRatioFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::LONG_SHORT_RATIO);
  }
  void onTopLongShortPositionRatioFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::LONG_SHORT_RATIO);
  }
  void onGlobalLongShortAccountRatioFeed(const FeedUpdate& upd) override {
    consumers_[upd.mea()]->Consume(upd, FeedType::LONG_SHORT_RATIO);
  }


  void Write() {
    for (auto& [mea, consumer] : consumers_) {
      LOG(INFO) << "Writing " << mea.String();
      consumer->Write(outfiles_[mea], config_.writer_option);
    }
  }

 private:
  IntervalFeedConfig config_;
  std::unordered_map<MarketExchangeApi, std::string> outfiles_;
  std::unordered_map<MarketExchangeApi, std::unique_ptr<IntervalFeedConsumer>> consumers_;
};

void ResolveAllPaths(
    const cxxopts::ParseResult& res,
    const DriverConfig& app,
    IntervalFeedConfig* config) {
  for (const auto& [key, exchange_config] : app.feed().exchanges()) {
    (void)key;
    std::string resolution_str;
    GetResolutionStr(config->resolution, &resolution_str);

    const std::string& mea = exchange_config.mea();
    std::string outfile;
    std::string mea_str;
    if (config->group != "ALL") {
      mea_str = mea + "_" + config->group;
    } else {
      mea_str = mea;
    }
    GetIntervalFeedPathOrDie(
        res["interval_feed_root"].as<std::string>(),
        "coin",
        res["interval_feed_machine"].as<std::string>(),
        resolution_str,
        mea_str,
        res["trading_date"].as<std::string>(),
        &outfile);
    std::cout << mea_str << ":" << outfile;
    config->outfiles[mea] = outfile;
  }
}

void Parse(int argc, char* argv[], DriverConfig* app, IntervalFeedConfig* config) {
  cxxopts::Options opt("Feed Plot", "Plot feed");
  AddDriverOptions(&opt);  // --driver
  opt.add_options()        // add options
      ("trading_date", "trading_date", cxxopts::value<std::string>())(
          "resolution",
          "length of interval in sec",
          cxxopts::value<int>()->default_value(
              "60"))("interval_feed_root", "interval_feed_root", cxxopts::value<std::string>())(
          "interval_feed_machine",
          "interval_feed_machine",
          cxxopts::value<std::string>()->default_value("main"))(
          "enable_time",
          "whether write time h5",
          cxxopts::value<bool>()->default_value("true"))(
          "enable_ohlc",
          "whether write ohlc h5",
          cxxopts::value<bool>()->default_value("true"))(
          "enable_vwap",
          "whether write vwap h5",
          cxxopts::value<bool>()->default_value("true"))(
          "enable_other_stats",
          "whether write other stats h5",
          cxxopts::value<bool>()->default_value("true"))(
          "enable_volume",
          "whether write volume h5",
          cxxopts::value<bool>()->default_value("true"))(
          "enable_market_analysis",
          "whether write Marker Analysis h5",
          cxxopts::value<bool>()->default_value("false"))(
          "duration", 
          "archive duration time", 
          cxxopts::value<std::string>()->default_value("P1D"))(
          "group", 
          "group of exchange", 
          cxxopts::value<std::string>()->default_value("ALL"));

  auto res = opt.parse(argc, argv);
  CHECK(ParseDriverOptions(res, app)) << "Fail to parse driver";
  auto* interval_config =
      app->mutable_feed()->mutable_common()->mutable_archive()->mutable_interval();
  int64_t start_ts = TimestampFromIso8601(res["trading_date"].as<std::string>());
  int64_t duration = DurationFromIso8601(res["duration"].as<std::string>());
  interval_config->set_start(std::to_string(start_ts));
  interval_config->set_duration(res["duration"].as<std::string>());

  config->start_ts = start_ts;
  config->duration = duration;
  config->resolution = res["resolution"].as<int>();
  config->group = res["group"].as<std::string>();
  config->writer_option.enable_time = res["enable_time"].as<bool>();
  config->writer_option.enable_ohlc = res["enable_ohlc"].as<bool>();
  config->writer_option.enable_vwap = res["enable_vwap"].as<bool>();
  config->writer_option.enable_other_stats = res["enable_other_stats"].as<bool>();
  config->writer_option.enable_volume = res["enable_volume"].as<bool>();
  config->writer_option.enable_market_analysis = res["enable_market_analysis"].as<bool>();
  ResolveAllPaths(res, *app, config);
}

}  // namespace interval_feed

int main(int argc, char* argv[]) {
  coin2::app::DriverConfig app;
  interval_feed::IntervalFeedConfig config;
  Parse(argc, argv, &app, &config);
  ::coin2::base::InitLogging(argv[0], app.log());

  interval_feed::GeneratingIntervalFeedStrategy strategy(config);
  ::coin2::app::Driver driver(app, &strategy);
  driver.Run();

  LOG(INFO) << "Finish reading feed";
  strategy.Write();

  return 0;
}
